package org.greenstone.gsdl3.service;

import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.w3c.dom.Document;
import org.w3c.dom.Element;


public class Koru1DocumentSurrogate {

	
	String docHash ;
	String docText ;
	Vector terms ;
	HashMap clauses ;
	TreeSet matches ;
	
	public Koru1DocumentSurrogate(String docText, String docHash, Vector terms) throws Exception {
		this.docText = docText ;
		this.docHash = docHash ;
		this.terms = terms ;
		
		this.clauses = new HashMap() ;
		this.matches = new TreeSet() ;
		
		calculateMatches() ;
	}
	
	protected String getTermFromIndex(int index) {
		return (String) terms.elementAt(index) ;
	}
	
	private void calculateMatches() {
		
		Iterator iter = terms.iterator() ;
		//int groupCount = 0 ;
		
		//matches = new TreeSet() ;
		int termIndex = 0 ;
		
		while (iter.hasNext()) {
			String term = (String) iter.next() ;
			
			Vector matchPositions = calculateMatchesForTerm(term, termIndex) ;
			
			Iterator iter3 = matchPositions.iterator() ;
			while (iter3.hasNext()) {
				Integer matchPos = (Integer) iter3.next() ;
				Match match = new Match(termIndex, matchPos) ;
				matches.add(match) ;
			}
			termIndex++ ;
		}
	}
	
	private Vector calculateMatchesForTerm(String term, int termIndex) {
		
		Vector matchPositions = new Vector() ;
		
		String p = "" ;
		for (int i=0 ; i<term.length() ; i++) {
			char l = Character.toLowerCase(term.charAt(i)) ;
			char u = Character.toUpperCase(l) ;
			p = p + "[" + l + u + "]" ;
		}
		String validBoundary = "[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]" ;
		
		p = validBoundary + p + validBoundary ;
		
		Pattern pattern = Pattern.compile(p);
		Matcher matcher = pattern.matcher(docText) ;
		
		while (matcher.find()){
			Match match = new Match(termIndex, new Integer(matcher.start()+1)) ;
			matches.add(match) ;
			updateClauses(docText, match) ;
		}
		
		return matchPositions ;
	}
	
	private void updateClauses(String docText, Match match) {
		
		int actualPos = match.pos.intValue() ;
		int length = getTermFromIndex(match.termIndex).length() ;
		
		int startRegion = Math.max(0, actualPos - 100) ;
		int endRegion = Math.min(docText.length(), actualPos + 100) ;
		int startPos, endPos ;
		
		Pattern pattern = Pattern.compile("[^ &&^\\W]") ;
		Matcher m = pattern.matcher(docText.substring(actualPos + length, endRegion)) ;
		if (m.find()) {
			if (m.start() == actualPos) {
				if (m.find()) 
					endPos = actualPos + m.start() + length ;
				else
					endPos = endRegion ;
			}else
				endPos = actualPos + m.start()+ length ;
		} else 
			endPos = endRegion ;
		
		String reversed = "" ;
		for (int i=startRegion ; i < actualPos ; i++)
			reversed = docText.substring(i, i+1) + reversed ;
		
		m = pattern.matcher(reversed) ;
		if (m.find())
			if (m.start() < 2) {
				if (m.find())
					startPos = actualPos - m.start() ;
				else
					startPos = startRegion ;
			} else
				startPos = actualPos - m.start() ;
		else
			startPos = startRegion ;
		
		//System.out.println(startPos + " " + actualPos + " " + endPos) ;
		
		String clauseText = docText.substring(startPos, endPos) ;
		clauseText = clauseText.replace('\'', '_') ;
		
		/*
		 docText.substring(startPos, actualPos) + 
		 "<em class=\"highlight\"" +
		 "style=\"background-color: " + match.phrase.color + "\"" +		    
		 docText.substring(actualPos, actualPos + length) + "</em>" +
		 docText.substring(actualPos + length, endPos) ;    
		 */
		
		Integer key = new Integer(startPos) ;
		Clause clause ;
		
		if (clauses.containsKey(key)) {
			clause = (Clause) clauses.get(key) ;
			clause.addMatch(match) ;
		} else {
			clause = new Clause(match, clauseText, startPos) ;
			clauses.put(key, clause) ;
		}
		
		match.clause = clause ;
	}
	
	public Element getDocumentXML(Document doc) {
		
		Element docXML = doc.createElement("Document") ;
		
		String reformattedText = "" ;
		int lastCopyPoint = 0 ;
		
		Iterator iter = matches.iterator() ;
		while (iter.hasNext()) {
			Match match = (Match)iter.next() ;
			
			int pos = match.pos.intValue() ;
			int length = match.length.intValue() ;
			
			if (pos > lastCopyPoint) {
				reformattedText = reformattedText + docText.substring(lastCopyPoint, pos) ;
				reformattedText = reformattedText + 
				"<em class=\"highlight\"" +
				"style=\"background-color: color" + match.termIndex + "\"" +
				" id=\"highlight" + match.termIndex + "\" >" ;
				
				reformattedText = reformattedText + docText.substring(pos, pos + length) ;
				reformattedText = reformattedText + "</em>" ;
				
				lastCopyPoint = pos + length ;
			}
		}
		
		reformattedText = reformattedText + docText.substring(lastCopyPoint) ;
		
		docXML.setAttribute("text", reformattedText) ;
		return docXML ;
	}
	
	public Element getSnippetXML(Document doc, int maxWords) {
		
		Element snippetXML = doc.createElement("Snippet") ;
		snippetXML.setAttribute("text", getSnippetText(maxWords)) ;
		return snippetXML ;
	}
	
	public String getSnippetText(int maxWords) {
		
		TreeSet sortedSnippets = new TreeSet() ;
		
		Iterator i = clauses.values().iterator() ;
		while (i.hasNext()) {
			Clause clause = (Clause) i.next() ;
			Snippet snippet = new Snippet(clause) ;
			sortedSnippets.add(snippet) ;
		}
		
		//System.out.println(clauses.values().size() + " clauses to " + sortedSnippets.size() + " snippets") ;
		if (sortedSnippets.isEmpty())
			return "no document snippets available" ;
		
		TreeSet chosenClauses = new TreeSet() ;
		int wordsUsed = 0 ;
		
		
		Snippet bestSnippet = (Snippet) sortedSnippets.first() ;
		chosenClauses.add(bestSnippet.clause) ;
		wordsUsed = bestSnippet.words ;
		
		boolean[] topicsNotMatched = bestSnippet.getTermsNotMatched(null) ;
		
		sortedSnippets.remove(bestSnippet) ;
		
		while (wordsUsed < maxWords) {
			bestSnippet = null ;
			
			i = sortedSnippets.iterator() ;
			while (i.hasNext()) {
				Snippet snippet = (Snippet) i.next() ;
				
				if (snippet.isBetterThan(bestSnippet, topicsNotMatched))
					bestSnippet = snippet ;
			}
			
			if (bestSnippet == null)
				break ;
			else {
				sortedSnippets.remove(bestSnippet) ;
				chosenClauses.add(bestSnippet.clause) ;
				topicsNotMatched = bestSnippet.getTermsNotMatched(topicsNotMatched) ;
				wordsUsed = wordsUsed + bestSnippet.words ;
			}
		}
		
		String text = "" ;
		i = chosenClauses.iterator() ;
		while (i.hasNext()) {
			Clause clause = (Clause) i.next() ;
			text = text + "... " + clause.text ;
		}
		
		return text ;
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	private class Clause implements Comparable {
		
		public TreeSet matches ;
		public String text ;
		public int start ;
		public TreeSet terms ;
		
		public Clause(Match match, String text, int start) {
			this.matches = new TreeSet() ;
			this.matches.add(match) ;
			this.text = " " + text + " " ;
			this.start = start ;
			this.terms = new TreeSet() ;
			this.addMatch(match) ;
		}
		
		public void addMatch(Match match) {
			this.matches.add(match) ;
			
			String term = getTermFromIndex(match.termIndex) ;
			
			if (!this.terms.contains(term)) {
				this.terms.add(term) ;
				this.highlightTerm(match.termIndex, term) ;
			}
		}
		
		public void highlightTerm(int termIndex, String term) {
			//System.out.println("Highlighting " + phrase.text + "in " + text) ;
			String reformattedText = "" ;
			int length = term.length() ;
			int lastCopyPoint = 0 ;
			
			String p = "" ;
			for (int i=0 ; i<length ; i++) {
				char l = Character.toLowerCase(term.charAt(i)) ;
				char u = Character.toUpperCase(l) ;
				p = p + "[" + l + u + "]" ;
			}
			String validBoundary = "[^ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789]" ;
			
			p = validBoundary + p + validBoundary ;
			
			Pattern pattern = Pattern.compile(p);
			Matcher matcher = pattern.matcher(text) ;
			
			while(matcher.find()) {
				int pos = matcher.start() + 1 ;
				
				reformattedText = reformattedText + text.substring(lastCopyPoint, pos) + 
				"<em class=\"highlight\"" +
				"style=\"background-color: color" + termIndex + "\">" +		    
				text.substring(pos, pos + length) + "</em>" ;    
				
				lastCopyPoint = pos + length ;
			}
			
			reformattedText = reformattedText + text.substring(lastCopyPoint) ;
			text = reformattedText ;
		}
		
		public int compareTo(Object o) {
			Clause c = (Clause) o ;
			return new Integer(start).compareTo(new Integer(c.start)) ;
		}
	}


	
	
	
	private class Match implements Comparable {
		
		public int termIndex ;
		public Integer pos ;
		public Integer length ;
		public Clause clause ;
		
		public Match(int termIndex, Integer pos) {
			this.termIndex = termIndex ;
			this.pos = pos ;
			this.length = new Integer(getTermFromIndex(termIndex).length()) ;
			
			if (pos == null)
				System.out.println("WTF???") ;
		}
		
		public int compareTo(Object o) {
			Match m = (Match) o ;
			
			int val = pos.compareTo(m.pos) ;
			
			if (val == 0) {
				val = m.length.compareTo(length) ;
				if (val == 0)
					return 1 ;
				else
					return val ;
			} else 
				return val ;
		}
	} 
	
	
	private class Snippet implements Comparable {
		
		Clause clause ;
		int words ;
		
		int totalTermMatches ;
		int uniqueTermMatches ;
		int termMatches[] ;
		
		public Snippet(Clause clause) {
			this.clause = clause ;	    	    
			this.words = clause.text.split(" ").length ;
			
			this.totalTermMatches = 0 ;
			this.uniqueTermMatches = 0 ;
			this.termMatches = new int[terms.size()] ;
			
			Iterator i = clause.matches.iterator() ;
			while (i.hasNext()) {
				Match match = (Match) i.next() ;
				this.addMatch(match) ;
			}
		}
		
		public void addMatch(Match match) {
			
			totalTermMatches++ ;
			
			if (termMatches[match.termIndex] == 0)
				uniqueTermMatches ++ ;
			
			termMatches[match.termIndex] ++ ;
		}
				
		public boolean[] getTermsNotMatched(boolean oldTermsNotMatched[]) {
			
			boolean newTermsNotMatched[] = new boolean[termMatches.length] ; 
			
			for(int i = 0 ; i < termMatches.length ; i++) {
				if (oldTermsNotMatched != null && oldTermsNotMatched[i]==false)
					newTermsNotMatched[i] = false ;
				else 
					newTermsNotMatched[i] = (termMatches[i] == 0) ;
			}
			
			return newTermsNotMatched ;
		}
		
		public boolean isBetterThan(Snippet snippet, boolean[] includeTopics) {
			
			if (snippet == null)
				return true ;
			
			int totalTermMatches1, totalTermMatches2 ;			// total term matches
			int uniqueTermMatches1, uniqueTermMatches2 ;		// unique topic matches
			//int upm1, upm2 ;
			
			if (includeTopics == null) {
				totalTermMatches1 = totalTermMatches ;

				uniqueTermMatches1 = uniqueTermMatches ;
				
				totalTermMatches2 = snippet.totalTermMatches ;
				uniqueTermMatches2 = snippet.uniqueTermMatches ;
			} else {
				totalTermMatches1 = totalTermMatches2 = 0 ;
				uniqueTermMatches1 = uniqueTermMatches2 = 0 ;
								
				for (int i=0 ; i<termMatches.length ; i++) {
					if (includeTopics[i]) {
						
						totalTermMatches1 = totalTermMatches1 + termMatches[i] ;
						totalTermMatches2 = totalTermMatches2 + snippet.termMatches[i] ;
						
						if (termMatches[i] > 0)
							uniqueTermMatches1 ++ ;
						
						if (snippet.termMatches[i] > 0)
							uniqueTermMatches2 ++ ;
					}
				}
			}
			// first preference goes to which one has more unique desired topics
			if (uniqueTermMatches1 > uniqueTermMatches2)
				return true ;
			
			if (uniqueTermMatches2 > uniqueTermMatches1)
				return false ;
			
			// next preference goes to which one has more total desired terms
			if (totalTermMatches1 > totalTermMatches2)
				return true ;
			
			if (totalTermMatches2 > totalTermMatches1)
				return false ;			
			
			int idealLength = 10 ;
			int length1 = Math.abs(idealLength - words) ;
			int length2 = Math.abs(idealLength - snippet.words) ;
			
			// next preference goes to clause closest to ideal length
			if (length1 < length2)
				return true ;
			
			if (length2 < length1)
				return false ;	    
			
			// final preference goes to clause that comes first in document
			if (clause.start < snippet.clause.start)
				return true ;
			
			return false ;
		}
		
		public int compareTo(Object o) {
			// used for sorting, does not necissarily indicate clause is better or worse
			Snippet snippet = (Snippet) o ;
			
			if (clause.matches.size() > snippet.clause.matches.size())
				return -1 ;
			
			if (clause.matches.size() < snippet.clause.matches.size())
				return 1 ;
			
			if (clause.start < snippet.clause.start)
				return -1 ;
			
			if (clause.start > snippet.clause.start)
				return 1 ;
			
			return 0 ;
		}
	}
}
